package com.dong.biz;

import com.dong.utils.SubscriberUtil;
import org.junit.Test;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import redis.clients.jedis.Jedis;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 * 买票
 * </p>
 *
 * @author: DJ
 * @Date: 14:49 2022/7/13
 */
public class TicketTest {

    /**
     * 共享数据 JVM内存模型的堆中
     * 1.操作的原子性 2.变量的可见性 3.编译的有序性
     */
    public static int count = 100;

    // redis key
    public static final String KEY = "key";

    // redis channel name
    public static final String CHANNEL_NAME = "channel_name";
    /**
     * 锁
     */
    static Lock lock = new ReentrantLock();

    // 存储随机值变量，并在同一个线程的两个方法中传递
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    @Resource
    JedisConnectionFactory factory;

    // 定时任务执行器线程池
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);

    // 存放每次加锁所创建的定时任务
    static ConcurrentHashMap<String, Future> futuresMap = new ConcurrentHashMap<String, Future>();

    @Test
    public void connect() {
        Jedis jedis = new Jedis("10.10.3.102", 6377);
        jedis.auth("1q2w3e4r");
        String set = jedis.set("name", "dongjian", "NX", "PX", 10000);
        System.out.println(set);
    }

    @Test
    public void TicketTest() throws InterruptedException {
        TicketRunnable tr = new TicketRunnable();
        Thread t1 = new Thread(tr, "窗口A");
        Thread t2 = new Thread(tr, "窗口B");
        Thread t3 = new Thread(tr, "窗口C");
        Thread t4 = new Thread(tr, "窗口D");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        Thread.currentThread().join();
    }

    public class TicketRunnable implements Runnable {
        @Override
        public void run() {
            while (count > 0) {
                lock.lock();
                try {
                    if (count > 0) {
                        System.out.println(Thread.currentThread().getName() + "售出第" + (count--) + "张票");
                    }
                } finally {
                    lock.unlock();
                }

                try {
                    // 模拟购票的中间消耗时间
                    TimeUnit.MICROSECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 阻塞式加锁
     */
    public void lock() {
        while (true) {
            if (tryLock()) {
                return;
            }
            // 创建消息订阅者 线程间的协作机制
            CountDownLatch cdl = new CountDownLatch(1);
            // 定义订阅对象
            final SubscriberUtil subscriberUtil = new SubscriberUtil(cdl);
            final Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    // 创建redis 通道监听
                    jedis.subscribe(subscriberUtil, CHANNEL_NAME);
                }
            }).start();

            try {
                // 阻塞等待别的线程解锁
                cdl.await(30, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            cdl = null;
            // 加锁之前取消订阅
            subscriberUtil.unsubscribe();
        }
    }

    /**
     * 非阻塞式加锁，使用setNx命令返回OK则加锁成功，生产随机值，并建立心跳检测
     *
     * @return
     */
    public boolean tryLock() {
        String uuid = threadLocal.get();
        if (threadLocal.get() == null) {
            uuid = UUID.randomUUID().toString();
            threadLocal.set(uuid);
        }
        Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();
        String ret = jedis.set(KEY, uuid, "NX", "PX", 30000);
        if ("OK".equals(ret)) {
            // 加锁成功后，开启心跳检测，加续有效期
            setHeartBeat(uuid);
            jedis.close();
            return true;
        }
        // 不是返回OK则是redis加锁失败
        jedis.close();
        return false;
    }

    /**
     * 心跳检测，缓存加续有效期
     *
     * @param uuid
     */
    private void setHeartBeat(final String uuid) {
        // 如果已经有心跳检测，则直接返回
        if (futuresMap.containsKey(uuid)) {
            return;
        }
        // 启动20s一次的心跳检测
        Future future = scheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Jedis jedis = (Jedis) factory.getConnection().getNativeConnection();
                if (jedis.get(KEY).equals(uuid)) {
                    // 如果uuid存在缓存中则加续 30s
                    jedis.expire(uuid, 30);
                } else {
                    // 如果当前任务的加锁节点不存在缓存中，则说明锁已经释放，则将心跳任务取消
                    futuresMap.get(uuid).cancel(true);
                    futuresMap.remove(uuid);
                }
                jedis.close();
            }
        }, 1, 20, TimeUnit.SECONDS);
        // 心跳检测程序启动后将对应的任务存储到map中
        futuresMap.put(uuid, future);
    }
}
